前言
本专题旨在快速了解常见的数据结构和算法。
在需要使用到相应算法时,能够帮助你回忆出常用的实现方案并且知晓其优缺点和适用环境。并不涉及十分具体的实现细节考究。
字符串排序算法简介
对于许多排序应用,决定顺序的键都是字符串。
其主要思想是利用比较,根据字符的有限性通过计数的方式来划分字符串的排名位置。
主要介绍以下几种方式:
- 预备知识:键索引计数法
- 低位优先的字符串排序 LSD string sort
- 高位优先的字符串排序 MSD string Sort
- 三向字符串快速排序 Three-way string quicksort
字符串排序算法要求大家先理解:基数排序和计数排序
常用方法
预备知识:键索引计数法
首先我们需要了解一个预备知识:键索引计数法
键索引计数法作为三种字符串排序算法中两种的基础,本身也很适用于小整数键的简单排序。
键索引计数法主要分为四步:统计频率,将频率转换为索引,数据分类,回写。
原理图:
举例说明:
比如数组a={1,2,3,4,2,3,4,2,1,3,4,2,3,4}
,它里面重复的数字比较多,不重复的只有1,2,3,4,这时就可以用此方法。
(例子来源:https://www.jianshu.com/p/be5b67139396)
- 频率统计
统计各个数字出现的次数,
1 | 1出现了2次 |
需要用一个5位的数组记录(比所需数字多一位),原因留给各位看官思考。
- 将频率转化为索引
前面我们记录了各自数字的次数,并用数组保存
1 | a[0]=0, |
这里从1开始计数,而不是从0,并不是为了与排序的数字对应,而是为了计算索引的方便,任意键的起始索引均为所有较小键的频率之和,我们就可以a[i+1]+=a[i]递推得到,这样a[0]=0,a[1]=2,a[2]=6,a[3]=10,a[4]=14,这样第一个数字(即1)的起始位置为 0,第2个数字(即 2)的起始位置为2……
多一个位置的原因:好处已经体现出来了,第一个就是用来标记最开始的起始位置的
- 数据分类
得到各个数字的起始索引,接下来就是将原数组进行归类,将相同的数字放在一起,这里我们用一个临时的数组进行记录
- 回写回原数组
最后将临时数组中的值写会原数组
代码实现:
1 | public class countSort { |
低位优先的字符串排序 LSD string sort
定义:
- 待排序的字符串长度:W
适用范围:
低位优先排序在我们的生活中经常见到,比如银行卡号的排序、车牌的排序以及电话号码的排序等
原理:
从右向左以每个字符作为关键字,用键索引计数法将字符串排序W次。由于计数排序法是稳定的,所以低位优先的字符串排序能够稳定地将字符串排序。
轨迹图:
代码实现:JAVA
摘自:https://www.cnblogs.com/sun-haiyu/p/7877651.html
算法(第四版)也有实现
1 | import java.util.Arrays; |
上面程序将打印如下内容
1 | [1ICK750, 1ICK750, 1OHV845, 1OHV845, 1OHV845, 2IYE230, 2RLA629, 2RLA629, 3ATW723, 3CIO720, 3CIO720, 4JZY524, 4PGC938] |
高位优先的字符串排序 MSD string Sort
参考:
https://blog.csdn.net/weixin_41427400/article/details/79851043
适用范围:
MSD与LSD比较起来,拥有更强的普适性,它不需要字符串的长度相同即可对字符串数组进行排序;
在生活中的使用也比LSD更多一些,比如字典里的排序就是MSD的情况,当然还有很多,这里就不再举例了。
原理:
MSD的核心思想是分治算法,即将大问题分为小问题来解决,其思想与快速排序类似。
先对最高位的字符进行排序,将排序后的字符串进行分组——最高位相同的在一组;在对同一组的进行MSD排序,不过此时以第二位字符进行排序,直到排完最低位,算法结束。(如图3所示)
思想讲起来总是很简单,不过当中的一些细节确实我们需要注意的。一个显而易见的问题是怎么处理结尾字符的问题,因为MSD运行字符的长度不同,那么总会有字符串先结束,这是我们就需要对这些字符串进行处理。如果我们每个字符都去判断显然会很麻烦,因此我们选择一种巧妙的方式使用一个CharAt(string, int)函数来返回字符串对应下标的字符,当对应下标不存在的时候我们返回-1;
1 |
|
这样我们就可以把字符串结尾的情况同其余情况一起处理,同时保证了已结尾的字符串会在未结尾的字符串之前!
代码实现:
详见算法(第四版)第五章或者如下网址C++实现:
https://blog.csdn.net/weixin_41427400/article/details/79851043
提升性能:
在数值排序中提到过一次优化排序效率的方法:当待排序数组的长度较小时,使用插入排序。同样的,该方法也适应与高位优先字符串排序,而且这种优化一般情况下也是必须的,有专家做过实验,在数据量巨大时,将长度小于10的子数组排序切换到插入排序,可以将排序的效率提升十倍左右。
三向字符串快速排序 Three-way string quicksort
MSD对包含大量重复键的字符串进行排序时,效率十分低下。三向字符串快速排序可以很好的解决这个问题,其是MSD和快速排序的结合版。
适用范围:
非常适用于有共同前缀的字符串
预备知识:三向切分的快速排序(数字快速排序的改进算法)
参考:
https://www.jianshu.com/p/0966f989974d
要理解三向字符串快速排序,需要先理解好三向切分的快速排序。
传统快速排序中,可能出现大量重复元素,最特殊的情况:一个数组中所有元素都相同,此时无需继续排序了,但是普通的快速排序算法还是会对数组进行切分。基于此可以将数组切分成三部分,分别对应小于、等于、大于切分元素的数组元素。
我们来看这种被称为三向切分的快速排序。它从左到右遍历数组一次,维护一个指针lt使得a[low…lt-1]中的元素都小于v,一个指针gt使得a[gt + 1…high]中的元素都大于v,一个指针i使得a[lt..i-1]中的元素都等于v,a[i..gt]中的元素暂定。一开始i和low相等。随着循环,a[i…gt]越来越小,即gt-i不断减小,当i > gt时循环结束。循环中进行下面的操作:
- 如果a[i]小于v,将a[i]和a[lt]交换,lt和i都加1;
- 如果a[i]大于v,将a[i]和a[gt]交换,gt减1;
- 如果a[i]等于v,将i加1
上面的这些操作保证了最后i > gt可以推出循环。
下面是三向切分快速排序的实现代码:
1 | public class Quick3way { |
对于存在大量重复元素的数组,这种方法比标准的快速排序要快。三向切分的最坏情况是所有元素各不相同,这时会比标准的快速排序要慢,因为比起标准的快速排序使用了更多的比较。
对于包含大量重复元素的数组,三向切分的快速排序算法将排序时间从线性对数级降低到线性级别,因此时间复杂度介于O(N)和O(Nlg N)之间,这依赖于输入数组中重复元素的数量。
三向字符串快速排序
我们可以利用上面学习的三向切分的数字快速排序思想,将字符串数组切分成三个子数组:
- 一个含有所有首字母小于切分字符的子数组
- 一个含有所有首字母等于切分字符的子数组
- 一个含有所有首字母大于切分字符的子数组。
然后递归地对这三个数组排序,要注意对于所有首字母等于切分字符的子数组,在递归排序时应该忽略首字母(就像MSD中那样)。
递归调用轨迹:
代码实现:
在三向切分的数字快速排序的基础上稍加修改
1 | import java.util.Arrays; |
三向切分的快速排序使用子数组的第一个元素作为切分点,三向切分的字符串快速排序使用子数组的第一个字符串的第d个字符作为切分字符。
在递归对子数组排序时,相比三向切分的快速排序,三向切分的字符串快速排序多了这么一个判断,这句的意思是只要还没到字符串的末尾(v = -1说明到达,其余均未到达),所有首字母与切分字符相等的子数组也需要递归排序,不过要像MSD那样,忽略掉相同的首字母,处理下一个字符。
总结
字符串排序算法选择:
参考
- 算法(第四版):第五章第一节
- https://www.cnblogs.com/sun-haiyu/p/7877651.html
- http://lixinzhang.github.io/string-sorts-zi-fu-chuan-pai-xu.html
- https://www.jianshu.com/p/be5b67139396
- https://blog.csdn.net/weixin_41427400/article/details/79851043
- https://blog.csdn.net/acmdream/article/details/74687035
- https://www.jianshu.com/p/0966f989974d
- https://www.jianshu.com/p/5beb3e73361d
关注我
我是蛮三刀把刀,后端开发。主要关注后端开发,数据安全,爬虫等方向。微信:yangzd1102
Github:@qqxx6661
个人博客:
原创博客主要内容
- Java知识点复习全手册
- Leetcode算法题解析
- 剑指offer算法题解析
- SpringCloud菜鸟入门实战系列
- SpringBoot菜鸟入门实战系列
- Python爬虫相关技术文章
- 后端开发相关技术文章
个人公众号:Rude3Knife
如果文章对你有帮助,不妨收藏起来并转发给您的朋友们~